package cz.vse.integrace.iag;

import com.google.api.client.auth.oauth2.draft10.AccessTokenResponse;
import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource;
import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessTokenRequest.GoogleAuthorizationCodeGrant;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import cz.vse.integrace.iag.db.ZamestnanciDAO;
import cz.vse.integrace.iag.db.Zamestnanec;
import cz.vse.integrace.iag.google.OAuthNastaveni;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJB;
import javax.ejb.Schedule;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Servlet starající se o zpětné volání OAuth od autentizačního serveru.
 * Získaný kód je převeden na autorizační a uložen.
 *
 * @author David Nejedly
 * @version 1.0
 */
public class OAuthCodeCallbackServlet extends HttpServlet {

    @EJB
    private ZamestnanciDAO zamestnanciDAO;
    /** The name of the Oauth code URL parameter */
    public static final String CODE_URL_PARAM_NAME = "code";
    /** The name of the OAuth error URL parameter */
    public static final String ERROR_URL_PARAM_NAME = "error";
    /** The URL suffix of the servlet */
    public static final String URL_MAPPING = "/integrace/oauth2callback";
    /** The URL to redirect the user to after handling the callback. Consider
     * saving this in a cookie before redirecting users to the Google
     * authorization URL if you have multiple possible URL to redirect people to. */
    public static final String REDIRECT_URL = "/integrace?action=stav";
    private static final HttpTransport TRANSPORT = new NetHttpTransport();
    private static final JsonFactory JSON_FACTORY = new JacksonFactory();

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        IntegracePageBean integracePageBean = getIntegracePageBean(req);
        integracePageBean.setObsah(Obsahy.STAV);
        // Getting the "error" URL parameter
        String[] error = req.getParameterValues(ERROR_URL_PARAM_NAME);

        // Checking if there was an error such as the user denied access
        if (error != null && error.length > 0) {
            integracePageBean.setOznameni("Nastala následující chyba: \"" + error[0] + "\".");
            resp.sendRedirect(req.getContextPath() + REDIRECT_URL);
            return;
        }
        // Getting the "code" URL parameter
        String[] code = req.getParameterValues(CODE_URL_PARAM_NAME);

        // Checking conditions on the "code" URL parameter
        if (code == null || code.length == 0) {
            integracePageBean.setOznameni("Chybí URL parametr \"code\". Získání pověření se nezdařilo.");
            resp.sendRedirect(req.getContextPath() + REDIRECT_URL);
            return;
        }

        // Construct incoming request URL
        String requestUrl = getOAuthCodeCallbackUrl(req);

        // Exchange the code for OAuth tokens
        AccessTokenResponse accessTokenResponse = exchangeCodeForAccessAndRefreshTokens(code[0],
                requestUrl);

        // Save the tokens
        zamestnanciDAO.ulozToken(accessTokenResponse, (Integer) req.getSession().getAttribute("zamId"));
        integracePageBean.setOznameni("Tokeny úspěšně uloženy.");
        resp.sendRedirect(req.getContextPath() + REDIRECT_URL);
    }

    /**
     * Construct the OAuth code callback handler URL.
     *
     * @param req the HttpRequest object
     * @return The constructed request's URL
     */
    public static String getOAuthCodeCallbackUrl(HttpServletRequest req) {
        String scheme = req.getScheme() + "://";
        String serverName = req.getServerName();
        String serverPort = (req.getServerPort() == 80) ? "" : ":" + req.getServerPort();
        String contextPath = req.getContextPath();
        String servletPath = URL_MAPPING;
        String pathInfo = (req.getPathInfo() == null) ? "" : req.getPathInfo();
        return scheme + serverName + serverPort + contextPath + servletPath + pathInfo;
    }

    /**
     * Exchanges the given code for an exchange and a refresh token.
     *
     * @param code The code gotten back from the authorization service
     * @param currentUrl The URL of the callback
     * @param oauthProperties The object containing the OAuth configuration
     * @return The object containing both an access and refresh token
     * @throws IOException
     */
    public AccessTokenResponse exchangeCodeForAccessAndRefreshTokens(String code, String currentUrl)
            throws IOException {

        HttpTransport httpTransport = new NetHttpTransport();
        JacksonFactory jsonFactory = new JacksonFactory();
        return new GoogleAuthorizationCodeGrant(httpTransport, jsonFactory, OAuthNastaveni.getClientId(), OAuthNastaveni.getClientSecret(), code, currentUrl).execute();
    }

    /**
     * Metoda, která najde integraceBean, pokud není nalezen, tak se vytvoří
     * nový.
     * 
     * @param request požadavek
     * @return nalezený integraceBean nebo nový
     * @throws ServletException
     */
    private IntegracePageBean getIntegracePageBean(HttpServletRequest request) throws ServletException {
        IntegracePageBean integraceBean = (IntegracePageBean) request.getSession().getAttribute("integraceBean");
        if (integraceBean == null) {
            return initializeIntegracePageBean(request.getSession());
        }
        return integraceBean;
    }

    /**
     * Metoda vytvářející page bean pro aktuální session.
     * 
     * @param session aktuální session
     * @return nový page bean pro integrace.jsp
     */
    private IntegracePageBean initializeIntegracePageBean(HttpSession session) {
        IntegracePageBean integracePageBean = new IntegracePageBean();
        integracePageBean.setObsah(Obsahy.STAV);
        integracePageBean.setOznameni("Vítejte!");
        session.setAttribute("integraceBean", integracePageBean);
        return integracePageBean;
    }

    /**
     * Metoda obnovujcí tokeny zaměstnanců každou půl hodinu.
     */
    @Schedule(minute = "*/30", hour = "*", persistent = false)
    private void refreshTokens() {
        for (Zamestnanec zam : zamestnanciDAO.getZamestnance()) {
            if ("".equals(zam.getAccessToken()) && "".equals(zam.getRefreshToken())) {
                GoogleAccessProtectedResource access = new GoogleAccessProtectedResource(zam.getAccessToken(),
                        TRANSPORT, JSON_FACTORY, OAuthNastaveni.getClientId(), OAuthNastaveni.getClientSecret(), zam.getRefreshToken());
                try {
                    // Obnova tokenu
                    access.refreshToken();
                    zam.setAccessToken(access.getAccessToken());
                    zam.setRefreshToken(access.getRefreshToken());
                    zamestnanciDAO.ulozZamestnance(zam);
                    Logger.getLogger(OAuthCodeCallbackServlet.class.getName()).log(Level.INFO, "Token pro {0} obnoven.", zam.getCeleJmeno());
                } catch (IOException ex) {
                    Logger.getLogger(OAuthCodeCallbackServlet.class.getName()).log(Level.SEVERE, "Nepodařilo se obnovit token pro " + zam.getCeleJmeno() + ".", ex);
                }
            }
        }

    }
}